package com.liuqianfei.mdurl;

import com.liuqianfei.util.ArrayUtil;
import com.liuqianfei.util.RegExpUtil;
import com.liuqianfei.util.StringUtil;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;

/**
 * @author liuqianfei
 */
public class Encode
{
    Map<String, String[]> encodeCache = new HashMap<>();
    String defaultChars   = ";/?:@&=+$,-_.!~*'()#";
    // String componentChars = "-_.!~*'()";


    // Create a lookup array where anything but characters in `chars` string
    // and alphanumeric chars is percent-encoded.
    //
    String[] getEncodeCache(String exclude)
    {
        int i;
        String ch;
        String[] cache = encodeCache.get(exclude);

        if (null != cache && cache.length > 0)
            return cache;

        cache = new String[128];

        for (i = 0; i < 128; i++)
        {
            ch = String.valueOf((char) i);

            if (RegExpUtil.match("^[0-9a-z]$", ch, true))
                // always allow unencoded alphanumeric characters
                cache[i] = ch;
            else
                cache[i] = "%" + StringUtil.slice("0" + Integer.toHexString(i).toUpperCase(), -2);
        }

        for (i = 0; i < exclude.length(); i++)
            cache = ArrayUtil.set(cache, exclude.charAt(i), String.valueOf(exclude.charAt(i)));

        encodeCache.put(exclude, cache);
        return cache;
    }


    // Encode unsafe characters with percent-encoding, skipping already
    // encoded sequences.
    //
    //  - string       - string to encode
    //  - exclude      - list of characters to ignore (in addition to a-zA-Z0-9)
    //  - keepEscaped  - don't encode '%' in a correct escape sequence (default: true)
    //
    public String encode(String string, String exclude, boolean keepEscaped)
    {
        int i, l;
        char code, nextCode;
        String[] cache;
        String result = "";

        if (null == exclude)
            exclude = this.defaultChars;

        cache = getEncodeCache(exclude);

        for (i = 0, l = string.length(); i < l; i++)
        {
            code = string.charAt(i);

            if (keepEscaped && code == 0x25 /* % */ && i + 2 < l)
            {
                String temp = StringUtil.slice(string, i + 1, i + 3);
                if (RegExpUtil.match("^[0-9a-f]{2}$", temp, true))
                {
                    result += StringUtil.slice(string, i, i + 3);
                    i += 2;
                    continue;
                }
            }

            if (code < 128)
            {
                result += cache[code];
                continue;
            }

            if (code >= 0xD800 && code <= 0xDFFF)
            {
                if (code >= 0xD800 && code <= 0xDBFF && i + 1 < l)
                {
                    nextCode = string.charAt(i + 1);
                    if (nextCode >= 0xDC00 && nextCode <= 0xDFFF)
                    {
                        try
                        {
                            result += URLEncoder.encode(string.substring(i, i + 2), "UTF-8");
                        }
                        catch (UnsupportedEncodingException e)
                        {
                            e.printStackTrace();
                        }
                        i++;
                        continue;
                    }
                }
                result += "%EF%BF%BD";
                continue;
            }

            try
            {
                result += URLEncoder.encode(string.substring(i, i + 1), "UTF-8");
            }
            catch (UnsupportedEncodingException e)
            {
                e.printStackTrace();
            }
        }

        return result;
    }
}
